package cn.edu.ptu.jobfairs.services.impl;

import cn.edu.ptu.jobfairs.basic.bo.*;
import cn.edu.ptu.jobfairs.basic.common.*;
import cn.edu.ptu.jobfairs.basic.dto.request.IdDTO;
import cn.edu.ptu.jobfairs.basic.dto.request.QueryDTO;
import cn.edu.ptu.jobfairs.basic.dto.request.ResumeQueryDTO;
import cn.edu.ptu.jobfairs.basic.dto.request.ResumeStatusDTO;
import cn.edu.ptu.jobfairs.basic.dto.response.ResumeDTO;
import cn.edu.ptu.jobfairs.basic.dto.response.StatisticsDTO;
import cn.edu.ptu.jobfairs.basic.dto.response.WrapResumeDTO;
import cn.edu.ptu.jobfairs.basic.enums.InterviewStatus;
import cn.edu.ptu.jobfairs.basic.enums.MemberStatus;
import cn.edu.ptu.jobfairs.core.SqlHandler;
import cn.edu.ptu.jobfairs.core.utils.SqlBuilder;
import cn.edu.ptu.jobfairs.core.utils.UserUtils;
import cn.edu.ptu.jobfairs.core.utils.ValidUtils;
import cn.edu.ptu.jobfairs.services.ResumeService;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cn.edu.ptu.jobfairs.basic.common.Errors.*;
import static cn.edu.ptu.jobfairs.basic.enums.InterviewStatus.*;

/**
 * @author wutingting.226@163.com
 * @date 2020/8/29
 */

@Service
@Transactional
public class ResumeServiceImpl implements ResumeService {
    @Resource
    private SqlHandler sqlHandler;

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result addResume(Resume resume) {
        // 建立简历
        if (resume == null || resume.getPositionId() == null) return Result.fail(INVALID_PARAMS);
        if (resume.getIdNumber() != null) {
            sqlHandler.update(SqlBuilder.from("update # set # where # and #")
                    .append(Resume.class).where("enable", 0)
                    .whereCrc("idNumber", resume.getIdNumber())
                    .where("positionId", resume.getPositionId()));
        }
        resume.setId(null);
        resume.setEnable(1);
        resume.setInterviewStatus(0);
        resume.setInterviewHandlerId(null);
        resume.setInterviewTime(null);
        resume.setSignStatus(0);
        resume.setSignHandlerId(null);
        resume.setSignTime(null);
        long time = System.currentTimeMillis();
        resume.setCreateTime(time);
        resume.setUpdateTime(time);
        Integer resumeId = sqlHandler.create(SqlBuilder.from("insert into # #")
                .append(Resume.class).insert(resume));
        return DataResult.ok(resumeId);
    }

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result editResume(Resume resume) {
        if (resume == null || resume.getId() == null) return Result.fail(INVALID_PARAMS);

        resume.setInterviewStatus(null);
        resume.setInterviewHandlerId(null);
        resume.setInterviewTime(null);
        resume.setSignStatus(null);
        resume.setSignHandlerId(null);
        resume.setSignTime(null);
        resume.setCreateTime(null);
        resume.setUpdateTime(System.currentTimeMillis());

        sqlHandler.updateOne(SqlBuilder.from("update # set # where # limit 1")
                    .append(Resume.class).update(resume).where(resume.getId()));
        return Result.OK;
    }

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result changeStatus(ResumeStatusDTO resumeStatusDTO) {
        if (resumeStatusDTO == null || resumeStatusDTO.getIsInterview() == null || resumeStatusDTO.getStatus() == null ||
                CollectionUtils.isEmpty(resumeStatusDTO.getResumeIds())) return Result.fail(INVALID_PARAMS);
        Resume resume = new Resume();
        long time = System.currentTimeMillis();
        resume.setUpdateTime(time);
        if (resumeStatusDTO.getIsInterview() == 1) {
            resume.setInterviewStatus(resumeStatusDTO.getStatus());
            resume.setInterviewTime(time);
            resume.setInterviewHandlerId(UserUtils.getId());
            //安排面试
            if (resumeStatusDTO.getStatus() == 1) {
                if (resumeStatusDTO.getInterviewId() == null) return Result.fail(INVALID_PARAMS);
                Interview interview = sqlHandler.select(SqlBuilder.from("select # from # where # limit 1")
                        .columns("arrangedTime").append(Interview.class).where(resumeStatusDTO.getInterviewId()), Interview.class);
                if (interview == null) return Result.fail(RECORD_NOT_EXISTS);
                List<Integer> existed = sqlHandler.selectAll(SqlBuilder.from("select # from # where # and #")
                        .columns("resumeId").append(InterviewRes.class)
                        .where("interviewId", resumeStatusDTO.getInterviewId())
                        .in("resumeId", resumeStatusDTO.getResumeIds()), InterviewRes.class)
                        .stream().map(InterviewRes::getResumeId).collect(Collectors.toList());
                List<InterviewRes> list = resumeStatusDTO.getResumeIds().stream().filter(item -> !existed.contains(item))
                        .map(item -> new InterviewRes(null, resumeStatusDTO.getInterviewId(), item))
                        .collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(list))
                    sqlHandler.insert(SqlBuilder.from("insert into # #").append(InterviewRes.class)
                        .insert(list));

                resume.setInterviewTime(interview.getArrangedTime());
            }
            else if (resumeStatusDTO.getStatus() == 0) {
                if (resumeStatusDTO.getInterviewId() == null) return Result.fail(INVALID_PARAMS);
                sqlHandler.delete(SqlBuilder.from("delete from # where # and #")
                        .append(InterviewRes.class).where("interviewId", resumeStatusDTO.getInterviewId())
                        .in("resumeId", resumeStatusDTO.getResumeIds()));
            }
        }
        else {
            resume.setSignStatus(resumeStatusDTO.getStatus());
            resume.setSignTime(time);
            resume.setSignHandlerId(UserUtils.getId());
            if (resumeStatusDTO.getStatus() == 1) {
                if (resumeStatusDTO.getSignTime() == null) return Result.fail(INVALID_PARAMS);
                resume.setSignTime(resumeStatusDTO.getSignTime());
            }
        }
        sqlHandler.update(SqlBuilder.from("update # set # where #")
                .append(Resume.class).update(resume).in(resumeStatusDTO.getResumeIds()));
        return Result.OK;
    }

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result delResume(IdDTO idDTO) {
        //hr权限 要求interviewStatus = 0 同时删除相应评价?
        if (ValidUtils.hasBlank(idDTO)) return Result.fail(INVALID_PARAMS);
        sqlHandler.delete(SqlBuilder.from("delete from # where #")
                .append(ResumeEvaluate.class).where("resumeId", idDTO.getId()));
        sqlHandler.delete(SqlBuilder.from("delete from # where #").append(InterviewRes.class)
                .where("resumeId", idDTO.getId()));
        sqlHandler.deleteOne(SqlBuilder.from("delete from # where # and # limit 1")
                .append(Resume.class).where(idDTO.getId()).where("interviewStatus", 0));
        return Result.OK;
    }

    @Override
    public Result listResumes(ResumeQueryDTO resumeQueryDTO) {
        // 判断user Status选择能看到的简历
        // 返回resume 但仅有字段: positionId name phone 和相应status/time/handlerId 和createTime
        if (resumeQueryDTO == null) return Result.fail(INVALID_PARAMS);
        User user = UserUtils.getUser();
        if (user == null) return Result.fail(NOT_LOGGED);
        SqlBuilder sqlBuilder = SqlBuilder.from("select # from #");
        sqlBuilder.columns( "id", "name", "education", "nativePlace", "positionId",
                "interviewStatus", "interviewTime", "signStatus", "signTime", "createTime")
                .append(Resume.class);
        if (ValidUtils.intEquals(user.getStatus(), MemberStatus.USER.getValue())) {
            //查找权限内的position
            List<MemberPos> pos = sqlHandler.selectAll(SqlBuilder
                    .from("select # from # where #")
                    .columns("positionId").append(MemberPos.class)
                    .where("memberId", user.getId()), MemberPos.class);
            if (CollectionUtils.isEmpty(pos)) return ListResult.EMPTY;
            if (CollectionUtils.isEmpty(resumeQueryDTO.getPositionIds())) {
                resumeQueryDTO.setPositionIds(pos.stream().map(MemberPos::getPositionId)
                        .collect(Collectors.toList()));
            }
            else {
                List<Integer> list = resumeQueryDTO.getPositionIds();
                resumeQueryDTO.setPositionIds(pos.stream().map(MemberPos::getPositionId)
                        .filter(list::contains).collect(Collectors.toList()));
            }
        }
        //evaluated特殊处理
        int type = getInt(resumeQueryDTO.getEvaluated());
        if (type == 1 || type == 2) {
            List<ResumeEvaluate> list = sqlHandler.selectAll(SqlBuilder.from("select # from # where #")
                    .columns("resumeId").append(ResumeEvaluate.class).where("memberId", user.getId()), ResumeEvaluate.class);
            if (CollectionUtils.isEmpty(list)) {
                if (type == 1) return ListResult.EMPTY;
            }
            else {
                List<Integer> ids = list.stream().map(ResumeEvaluate::getResumeId).collect(Collectors.toList());
                if (type == 1) sqlBuilder.and().in(ids);
                else sqlBuilder.and().notIn(ids);
            }
        }
        parseQuery(sqlBuilder, resumeQueryDTO);
        List<Resume> list = sqlHandler.selectAll(sqlBuilder, Resume.class);
        return ListResult.ok(list, sqlHandler.count(sqlBuilder.toCount()));
    }

    private int getInt(Integer i) {
        return i == null ? 0 : i;
    }

    private void parseQuery(SqlBuilder sqlBuilder, ResumeQueryDTO resumeQueryDTO) {
        sqlBuilder.and().where("enable", ValidUtils.intEquals(resumeQueryDTO.getEvaluated(), 4) ? 0 : 1);
        if (!CollectionUtils.isEmpty(resumeQueryDTO.getPositionIds()))
            sqlBuilder.and().in(Resume.class, "positionId", resumeQueryDTO.getPositionIds());
        if (!StringUtils.isEmpty(resumeQueryDTO.getName()))
            sqlBuilder.and().whereCrc(Resume.class, "name", resumeQueryDTO.getName());
        if (!CollectionUtils.isEmpty(resumeQueryDTO.getInterviewStatus()))
            sqlBuilder.and().in(Resume.class, "interviewStatus", resumeQueryDTO.getInterviewStatus());
        if (resumeQueryDTO.getInterviewTimeBegin() != null && resumeQueryDTO.getInterviewTimeEnd() != null)
            sqlBuilder.and().between(Resume.class, "interviewTime",
                    resumeQueryDTO.getInterviewTimeBegin(), resumeQueryDTO.getInterviewTimeEnd());
        if (resumeQueryDTO.getInterviewHandlerId() != null)
            sqlBuilder.and().where(Resume.class, "interviewHandlerId", resumeQueryDTO.getInterviewHandlerId());
        if (!CollectionUtils.isEmpty(resumeQueryDTO.getSignStatus()))
            sqlBuilder.and().in(Resume.class, "signStatus", resumeQueryDTO.getSignStatus());
        if (resumeQueryDTO.getSignTimeBegin() != null && resumeQueryDTO.getSignTimeEnd() != null)
            sqlBuilder.and().between(Resume.class, "signTime",
                    resumeQueryDTO.getSignTimeBegin(), resumeQueryDTO.getSignTimeEnd());
        if (resumeQueryDTO.getSignHandlerId() != null)
            sqlBuilder.and().where(Resume.class, "signHandlerId", resumeQueryDTO.getSignHandlerId());
        if (resumeQueryDTO.getCreateTimeBegin() != null && resumeQueryDTO.getCreateTimeEnd() != null)
            sqlBuilder.and().between(Resume.class, "createTime",
                    resumeQueryDTO.getCreateTimeBegin(), resumeQueryDTO.getCreateTimeEnd());
        sqlBuilder.concat(" group by #").append(Resume.class, "id")
                .concat(" order by # desc").append(Resume.class, "id");
        if (resumeQueryDTO.getLimit() != null)
            sqlBuilder.concat(" limit ").concat(resumeQueryDTO.getLimit());
        if (resumeQueryDTO.getOffset() != null)
            sqlBuilder.concat(" offset ").concat(resumeQueryDTO.getOffset());
    }

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result exportResumes(ResumeQueryDTO resumeQueryDTO) {
        if (resumeQueryDTO == null) return Result.fail(INVALID_PARAMS);
        SqlBuilder sqlBuilder = SqlBuilder.from("select # from #")
                .append(Resume.class, "positionId", "name", "phone", "gender", "birth", "nativePlace", "extJson");
        resumeQueryDTO.setLimit(null);
        resumeQueryDTO.setOffset(null);
        buildSql(resumeQueryDTO, sqlBuilder);
        List<Resume> list = sqlHandler.selectAll(sqlBuilder, Resume.class);
        return DataResult.ok(list);
    }

    @Override
    @PostAuthorize("hasAuthority('hr')")
    public Result listNotEvaluatedResumes(ResumeQueryDTO resumeQueryDTO) {
        // 0:全部 1:有人未评价 2:有院领导未评价 3:有系主任未评价 (123不计算人事处未评价) 4:无效简历(enable=0)
        if (resumeQueryDTO == null) return Result.fail(INVALID_PARAMS);
        SqlBuilder sqlBuilder = SqlBuilder.from("select # from #")
                .append(Resume.class, "id", "positionId", "name", "phone", "interviewStatus",
                        "interviewTime", "interviewHandlerId", "signStatus", "signTime", "signHandlerId", "createTime");
        buildSql(resumeQueryDTO, sqlBuilder);
        List<WrapResumeDTO> list = sqlHandler.selectAll(sqlBuilder, WrapResumeDTO.class);
        if (!CollectionUtils.isEmpty(list)) {
            List<ResumeEvaluate> evaluates = sqlHandler.selectAll(SqlBuilder.from("select # from # where #")
                    .columns("resumeId", "advise").append(ResumeEvaluate.class)
                    .in("resumeId", list.stream().map(WrapResumeDTO::getId).collect(Collectors.toList())), ResumeEvaluate.class);
            if (!CollectionUtils.isEmpty(evaluates)) {
                Map<Integer, WrapResumeDTO> map = list.stream().collect(Collectors.toMap(WrapResumeDTO::getId, v -> v));
                evaluates.forEach(e -> {
                    WrapResumeDTO v = map.get(e.getResumeId());
                    if (v != null) {
                        v.addEvaluate();
                        if (ValidUtils.intEquals(e.getAdvise(), 1)) v.addAccept();
                    }
                });
            }
        }
        return ListResult.ok(list, sqlHandler.count(sqlBuilder.toCount()));
    }

    private void buildSql(ResumeQueryDTO resumeQueryDTO, SqlBuilder sqlBuilder) {
        int type = getInt(resumeQueryDTO.getEvaluated());
        if (type > 0 && type < 4) {
            if (type == 1) {
                sqlBuilder.append(Member.class).concat(" left join # on #=# right join # on #=# and #=3 or #=2")
                        .append(MemberPos.class)
                        .append(Member.class, "id").append(MemberPos.class, "memberId")
                        .append(Resume.class)
                        .append(Resume.class, "positionId").append(MemberPos.class, "positionId")
                        .append(Member.class, "status").append(Member.class, "status");
            }
            else if (type == 2) {
                sqlBuilder.append(Resume.class).concat(" left join # on #=2")
                        .append(Member.class).append(Member.class, "status");
            }
            else if (type == 3) {
                sqlBuilder.append(Member.class).concat(" inner join # on #=# and #=3 right join # on #=#")
                        .append(MemberPos.class)
                        .append(Member.class, "id").append(MemberPos.class, "memberId")
                        .append(Member.class, "status")
                        .append(Resume.class)
                        .append(Resume.class, "positionId").append(MemberPos.class, "positionId");
            }
            sqlBuilder.concat(" left join # on #=# and #=#")
                    .append(ResumeEvaluate.class)
                    .append(Resume.class, "id").append(ResumeEvaluate.class, "resumeId")
                    .append(Member.class, "id").append(ResumeEvaluate.class, "memberId")
                    .and().append(ResumeEvaluate.class, "id").concat(" is null");
            //.and().append(Member.class, "id").concat(" is not null"); //如果该成员为空
        }
        else sqlBuilder.append(Resume.class);
        parseQuery(sqlBuilder, resumeQueryDTO);
    }

    private <T extends Resume> ListResult<T> packList(Class<T> clazz, ResumeQueryDTO resumeQueryDTO, SqlBuilder sqlBuilder) {
        List<T> list = sqlHandler.selectAll(sqlBuilder, clazz);
        return ListResult.ok(list, sqlHandler.count(sqlBuilder.toCount()));
    }

    @Override
    public Result listByInterviewId(IdDTO idDTO) {
        if (ValidUtils.hasBlank(idDTO)) return Result.fail(INVALID_PARAMS);
        return DataResult.ok(sqlHandler.selectAll(SqlBuilder.from("select # from # where id in (select # from # where #)")
                .columns("id", "name", "phone", "idNumber", "education", "nativePlace", "interviewStatus", "signStatus", "attachments")
                .append(Resume.class)
                .columns("resumeId").append(InterviewRes.class).where("interviewId", idDTO.getId()), Resume.class));
    }

    @Override
    public Result resumeContent(IdDTO idDTO) {
        if (ValidUtils.hasBlank(idDTO)) return Result.fail(INVALID_PARAMS);
        Resume resume = sqlHandler.select(SqlBuilder.from("select * from # where # limit 1")
                .append(Resume.class).where(idDTO.getId()), Resume.class);
        return resume == null ? Result.fail(RECORD_NOT_EXISTS) : DataResult.ok(resume);
    }

    @Override
    public Result resumeDetails(IdDTO idDTO) {
        if (ValidUtils.hasBlank(idDTO)) return Result.fail(INVALID_PARAMS);
        Resume resume = sqlHandler.select(SqlBuilder.from("select * from # where # limit 1")
                .append(Resume.class).where(idDTO.getId()), Resume.class);
        if (resume == null) return Result.fail(RECORD_NOT_EXISTS);
        List<ResumeEvaluate> resumeEvaluates = sqlHandler.selectAll(SqlBuilder.from("select * from # where #")
                .append(ResumeEvaluate.class).where("resumeId", idDTO.getId()), ResumeEvaluate.class);
        // 如果未安排面试, 为未评价的成员; 已安排则为面试者  只需要 name, number, title, phone 字段
        List<Member> members;
        if (InterviewStatus.of(resume.getInterviewStatus()) == SUBMIT) {
            members = sqlHandler.selectAll(SqlBuilder.from("select # from # left join # on # = # where (# = 3 and # = # or # = 2)" +
                            " and # not in (select # from # where #) group by #")
                            .append(Member.class, "name", "number", "title", "phone")
                            .append(Member.class).append(MemberPos.class)
                            .append(Member.class, "id").append(MemberPos.class, "memberId")
                            .append(Member.class, "status").append(MemberPos.class, "positionId")
                            .append(resume.getPositionId()).append(Member.class, "status")
                            .append(Member.class, "id").columns("memberId").append(ResumeEvaluate.class)
                            .where("resumeId", idDTO.getId()).append(Member.class, "id"), Member.class);
        }
        else {
            members = sqlHandler.selectAll(SqlBuilder.from("select # from # where id in (select # from # where # = " +
                    "(select # from # right join # on # = # where # = # order by # desc limit 1))")
                    .append(Member.class, "name", "number", "title", "phone")
                    .append(Member.class).columns("memberId").append(InterviewMem.class).columns("interviewId")
                    .append(Interview.class, "id").append(Interview.class).append(InterviewRes.class)
                    .append(Interview.class, "id").append(InterviewRes.class, "interviewId")
                    .append(InterviewRes.class, "resumeId").append(idDTO.getId())
                    .append(Interview.class, "arrangedTime"), Member.class);
        }
        ResumeDTO resumeDTO = new ResumeDTO();
        resumeDTO.setResume(resume);
        resumeDTO.setResumeEvaluates(resumeEvaluates);
        resumeDTO.setMembers(members);
        return DataResult.ok(resumeDTO);
    }

    @Override
    public Result statistics(QueryDTO queryDTO) {
        SqlBuilder sqlBuilder = SqlBuilder.from("select #, #, count(#) as #, count(#) as # from # right join # on #=# where (#=1 or # is null)")
                .append(Position.class, "id", "name")
                .append(Resume.class, "interviewStatus", "signStatus")
                .columns("interviewStatus").columns("interviewTime").columns("signStatus").columns("signTime")
                .append(Resume.class)
                .append(Position.class).append(Resume.class, "positionId").append(Position.class, "id")
                .append(Resume.class, "enable").append(Resume.class, "interviewStatus");
        if (queryDTO.getCreateTimeBegin() != null && queryDTO.getCreateTimeEnd() != null)
            sqlBuilder.concat(" and #").between("createTime", queryDTO.getCreateTimeBegin(), queryDTO.getCreateTimeEnd());
        sqlBuilder.concat(" group by #, #").append(Position.class, "id").columns("interviewStatus", "signStatus");
        List<Resume> resumes = sqlHandler.selectAll(sqlBuilder, Resume.class);
        Map<Integer, StatisticsDTO> map = new HashMap<>();
        for (Resume resume : resumes) {
            List<Long> collect = map.computeIfAbsent(resume.getId(), key -> new StatisticsDTO(resume.getName(),
                    Arrays.asList(0L, 0L, 0L, 0L, 0L))).getCollect();
            add(collect, 0, resume.getInterviewTime());
            if (resume.getInterviewStatus() == null) continue;
            if (!ValidUtils.intEquals(resume.getInterviewStatus(), 0))
                add(collect, 1, resume.getInterviewTime());
            if (ValidUtils.intEquals(resume.getInterviewStatus(), 2))
                add(collect, 2, resume.getInterviewTime());
            if (ValidUtils.intEquals(resume.getSignStatus(), 1))
                add(collect, 3, resume.getSignTime());
            if (ValidUtils.intEquals(resume.getSignStatus(), 2))
                add(collect, 4, resume.getSignTime());
        }
        return DataResult.ok(map.values());
    }

    private void add(List<Long> collect, int index, long offset) {
        collect.set(index, collect.get(index) + offset);
    }
}